/**
 * Java RTP Library (jlibrtp)
 * Copyright (C) 2006 Arne Kepp
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jlibrtp;

import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A participant represents a peer in an RTPSession. Based on the information stored on
 * these objects, packets are processed and statistics generated for RTCP.
 */
public class Participant {
    /** Logger instance. */
    private static final Logger LOGGER =
        Logger.getLogger(Participant.class.getName());

    /** Whether the participant is unexpected, e.g. arrived through unicast with SDES */
    protected boolean unexpected = false;
    /** Where to send RTP packets (unicast)*/
    protected InetSocketAddress rtpAddress = null;
    /** Where to send RTCP packets (unicast) */
    protected InetSocketAddress rtcpAddress = null;
    /** Where the first RTP packet was received from */
    protected InetSocketAddress rtpReceivedFromAddress = null;
    /** Where the first RTCP packet was received from */
    protected InetSocketAddress rtcpReceivedFromAddress = null;

    /** SSRC of participant */
    protected long ssrc = -1;
    /** SDES CNAME */
    protected String cname = null;
    /** SDES The participant's real name */
    protected String name = null;
    /** SDES The participant's email */
    protected String email = null;
    /** SDES The participant's phone number */
    protected String phone = null;
    /** SDES The participant's location*/
    protected String loc = null;
    /** SDES The tool the participants is using */
    protected String tool = null;
    /** SDES A note */
    protected String note = null;
    /** SDES A priv string, loosely defined */
    protected String priv = null;

    // Receiver Report Items
    /** RR First sequence number */
    protected int firstSeqNumber = -1;
    /** RR Last sequence number */
    protected int lastSeqNumber = 0;
    /** RR Number of times sequence number has rolled over */
    protected long seqRollOverCount = 0;
    /** RR Number of packets received */
    protected long receivedPkts = 0;
    /** RR Number of octets received */
    protected long receivedOctets = 0;
    /** RR Number of packets received since last SR */
    protected int receivedSinceLastSR = 0;
    /** RR Sequence number associated with last SR */
    protected int lastSRRseqNumber = 0;
    /** RR Interarrival jitter */
    protected double interArrivalJitter = -1.0;
    /** RR Last received RTP Timestamp */
    protected long lastRtpTimestamp = 0;

    /** RR Middle 32 bits of the NTP timestamp in the last SR */
    protected long timeStampLSR = 0;
    /** RR The time when we actually got the last SR */
    protected long timeReceivedLSR = 0;

    /** Gradient where UNIX timestamp = ntpGradient*RTPTimestamp * ntpOffset */
    protected double ntpGradient = -1;
    /** Offset where UNIX timestamp = ntpGradient*RTPTimestamp * ntpOffset */
    protected long ntpOffset = -1;
    /** Last NTP received in SR packet, MSB */
    protected long lastNtpTs1 = 0; //32 bits
    /** Last NTP received in SR packet, LSB */
    protected long lastNtpTs2 = 0; //32 bits
    /** RTP Timestamp in last SR packet */
    protected long lastSRRtpTs = 0; //32 bits

    /** UNIX time when a BYE was received from this participant, for pruning */
    protected long timestampBYE = -1;	// The user said BYE at this time

    /** Store the packets received from this participant */
    protected PktBuffer pktBuffer = null;

    /** UNIX time of last RTP packet, to check whether this participant has sent anything recently */
    protected long lastRtpPkt = -1; //Time of last RTP packet
    /** UNIX time of last RTCP packet, to check whether this participant has sent anything recently */
    protected long lastRtcpPkt = -1; //Time of last RTCP packet
    /** UNIX time this participant was added by application, to check whether we ever heard back */
    protected long addedByApp = -1; //Time the participant was added by application
    /** UNIX time of last time we sent an RR to this user */
    protected long lastRtcpRRPkt = -1; //Timestamp of last time we sent this person an RR packet
    /** Unix time of second to last time we sent and RR to this user */
    protected long secondLastRtcpRRPkt = -1; //Timestamp of 2nd to last time we sent this person an RR Packet

    /**
     * Create a basic participant. If this is a <b>unicast</b> session you must provide network address (ipv4 or ipv6) and ports for RTP and RTCP,
     * as well as a cname for this contact. These things should be negotiated through SIP or a similar protocol.
     *
     * jlibrtp will listen for RTCP packets to obtain a matching SSRC for this participant, based on cname.
     * @param networkAddress string representation of network address (ipv4 or ipv6). Use "127.0.0.1" for multicast session.
     * @param rtpPort port on which peer expects RTP packets. Use 0 if this is a sender-only, or this is a multicast session.
     * @param rtcpPort port on which peer expects RTCP packets. Use 0 if this is a sender-only, or this is a multicast session.
     */
    public Participant(String networkAddress, int rtpPort, int rtcpPort) {
        if(LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("Creating new participant: " + networkAddress);
        }

        // RTP
        if(rtpPort > 0) {
            try {
                rtpAddress = new InetSocketAddress(networkAddress, rtpPort);
            } catch (Exception e) {
                LOGGER.warning("Couldn't resolve " + networkAddress);
            }
            //isReceiver = true;
        }

        // RTCP
        if(rtcpPort > 0) {
            try {
                rtcpAddress = new InetSocketAddress(networkAddress, rtcpPort);
            } catch (Exception e) {
                LOGGER.warning("Couldn't resolve " + networkAddress);
            }
        }

        //By default this is a sender
        //isSender = true;
    }

    /**
     * We got a packet, but we don't know this person yet.
     * @param rtpAdr RTP received from address
     * @param rtcpAdr RTCP received from address
     * @param SSRC SSRC of participant
     */
    protected Participant(InetSocketAddress rtpAdr, InetSocketAddress rtcpAdr, long SSRC) {
        rtpReceivedFromAddress = rtpAdr;
        rtcpReceivedFromAddress = rtcpAdr;
        ssrc = SSRC;
        unexpected = true;
    }

    /**
     * Dummy constructor to ease testing
     */
    protected Participant() {
        LOGGER.warning("Don't use the Participan(void) Constructor!");
    }

    /**
     * RTP Address registered with this participant.
     *
     * @return address of participant
     */
    InetSocketAddress getRtpSocketAddress() {
        return rtpAddress;
    }


    /**
     * RTCP Address registered with this participant.
     *
     * @return address of participant
     */
    InetSocketAddress getRtcpSocketAddress() {
        return rtcpAddress;
    }

    /**
     * InetSocketAddress this participant has used to
     * send us RTP packets.
     *
     * @return address of participant
     */
    InetSocketAddress getRtpReceivedFromAddress() {
        return rtpAddress;
    }



    /**
     * InetSocketAddress this participant has used to
     * send us RTCP packets.
     *
     * @return address of participant
     */
    InetSocketAddress getRtcpReceivedFromAddress() {
        return rtcpAddress;
    }


    /**
     * CNAME registered for this participant.
     *
     * @return the cname
     */
    public String getCNAME() {
        return cname;
    }


    /**
     * NAME registered for this participant.
     *
     * @return the name
     */
    public String getNAME() {
        return name;
    }

    /**
     * EMAIL registered for this participant.
     *
     * @return the email address
     */
    public String getEmail() {
        return email;
    }

    /**
     * PHONE registered for this participant.
     *
     * @return the phone number
     */
    public String getPhone() {
        return phone;
    }

    /**
     * LOCATION registered for this participant.
     *
     * @return the location
     */
    public String getLocation() {
        return loc;
    }

    /**
     * NOTE registered for this participant.
     *
     * @return the note
     */
    public String getNote() {
        return note;
    }

    /**
     * PRIVATE something registered for this participant.
     *
     * @return the private-string
     */
    public String getPriv() {
        return priv;
    }

    /**
     * TOOL something registered for this participant.
     *
     * @return the tool
     */
    public String getTool() {
        return tool;
    }

    /**
     * SSRC for participant, determined through RTCP SDES
     *
     * @return SSRC (32 bit unsigned integer as long)
     */
    public long getSSRC() {
        return this.ssrc;
    }

    /**
     * Updates the participant with information for receiver reports.
     *
     * @param packetLength to keep track of received octets
     * @param pkt the most recently received packet
     */
    protected void updateRRStats(int packetLength, RtpPkt pkt) {
        int curSeqNum = pkt.getSeqNumber();

        if(firstSeqNumber < 0) {
            firstSeqNumber = curSeqNum;
        }

        receivedOctets += packetLength;
        receivedSinceLastSR++;
        receivedPkts++;

        long curTime =  System.currentTimeMillis();

        if( this.lastSeqNumber < curSeqNum ) {
            //In-line packet, best thing you could hope for
            this.lastSeqNumber = curSeqNum;

        } else if(this.lastSeqNumber - this.lastSeqNumber < -100) {
            //Sequence counter rolled over
            this.lastSeqNumber = curSeqNum;
            seqRollOverCount++;

        } else {
            //This was probably a duplicate or a late arrival.
        }

        // Calculate jitter
        if(this.lastRtpPkt > 0) {

            long D = (pkt.getTimeStamp() - curTime) - (this.lastRtpTimestamp - this.lastRtpPkt);
            if(D < 0)
                D = (-1)*D;

            this.interArrivalJitter += ((double)D - this.interArrivalJitter) / 16.0;
        }

        lastRtpPkt = curTime;
        lastRtpTimestamp = pkt.getTimeStamp();
    }

    /**
     * Calculates the extended highest sequence received by adding
     * the last sequence number to 65536 times the number of times
     * the sequence counter has rolled over.
     *
     * @return extended highest sequence
     */
    protected long getExtHighSeqRecv() {
        return (65536*seqRollOverCount + lastSeqNumber);
    }

    /**
     * Get the fraction of lost packets, calculated as described
     * in RFC 3550 as a fraction of 256.
     *
     * @return the fraction of lost packets since last SR received
     */
    protected int getFractionLost() {
        int expected = (lastSeqNumber - lastSRRseqNumber);
        if(expected < 0)
            expected = 65536 + expected;

        int fraction = 256 * (expected - receivedSinceLastSR);
        if(expected > 0) {
            fraction = (fraction / expected);
        } else {
            fraction = 0;
        }

        //Clear counters
        receivedSinceLastSR = 0;
        lastSRRseqNumber = lastSeqNumber;

        return fraction;
    }

    /**
     * The total number of packets lost during the session.
     *
     * Returns zero if loss is negative, i.e. duplicates have been received.
     *
     * @return number of lost packets, or zero.
     */
    protected long getLostPktCount() {
        long lost = (this.getExtHighSeqRecv() - this.firstSeqNumber) - receivedPkts;

        if(lost < 0)
            lost = 0;
        return lost;
    }

    /**
     *
     * @return the interArrivalJitter, calculated continuously
     */
    protected double getInterArrivalJitter() {
        return this.interArrivalJitter;
    }

    /**
     * Set the timestamp for last sender report
     *
     * @param ntp1 high order bits
     * @param ntp2 low order bits
     */
    protected void setTimeStampLSR(long ntp1, long ntp2) {
        // Use what we've got
        byte[] high = StaticProcs.uIntLongToByteWord(ntp1);
        byte[] low = StaticProcs.uIntLongToByteWord(ntp2);
        low[3] = low[1];
        low[2] = low[0];
        low[1] = high[3];
        low[0] = high[2];

        this.timeStampLSR = StaticProcs.bytesToUIntLong(low, 0);
    }

    /**
     * Calculate the delay between the last received sender report
     * and now.
     *
     * @return the delay in units of 1/65.536ms
     */
    protected long delaySinceLastSR() {
        if(this.timeReceivedLSR < 1)
            return 0;

        long delay = System.currentTimeMillis() - this.timeReceivedLSR;

        //Convert ms into 1/65536s = 1/65.536ms
        return (long) ((double)delay * 65.536);
    }

    /**
     * Only for debugging purposes
     */
    public void debugPrint() {
        StringBuilder str = new StringBuilder();
        str.append(" Participant.debugPrint() SSRC:"+this.ssrc+" CNAME:"+this.cname);
        if(this.rtpAddress != null)
            str.append(" RTP:"+this.rtpAddress.toString());
        if(this.rtcpAddress != null)
            str.append(" RTCP:"+this.rtcpAddress.toString());
        LOGGER.finest(str.toString());

        LOGGER.finest("                          Packets received:"+this.receivedPkts);
    }
}
